feat(rtl): RTL infrastructure — scan guard, codemod, useDirection, Storybook direction toolbar - #356
Merged
Merged
Conversation
…, Storybook direction toolbar - rtl-scan.mjs ratchet (CI-enforced, baseline at current main: 520 matches / 126 files) - rtl-codemod.mjs physical→logical class rewriter - useDirection hook + isRtlLocale (exported from hooks index) - cn() tailwind-merge conflicts so physical/logical pairs override correctly - Storybook direction toolbar (auto/ltr/rtl) + Arabic sample locale Part 1 of the RTL series (#319), superseding #320.
Deploying ui with
|
| Latest commit: |
d44fe9a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://ec39fac7.ui-6d0.pages.dev |
| Branch Preview URL: | https://feat-rtl-infra.ui-6d0.pages.dev |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds RTL (right-to-left) infrastructure to the UI component library: guardrails to prevent new physical-direction Tailwind utilities, a codemod to migrate classes, a runtime direction hook for JS behaviors, and Storybook globals to preview RTL.
Changes:
- Add an RTL scan “ratchet” guard (
pnpm rtl:scan) with a committed baseline and CI wiring. - Introduce an RTL codemod script for physical→logical Tailwind class rewrites (dry-run by default).
- Add
useDirection+ locale helpers, and updatecn()to treat physical/logical directional utilities as conflicting; add Storybookdirectiontoolbar and syncdir/langon<html>.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/utils/index.ts |
Re-export miewebUITwMergeConflicts alongside cn for consumers. |
src/utils/cn.ts |
Switch to extendTailwindMerge and add physical⇄logical conflict groups; export conflicts config. |
src/utils/cn.test.ts |
Add unit tests covering directional conflict behavior and baseline cn() merging. |
src/hooks/useDirection.ts |
Add useDirection hook plus RTL_LOCALES and isRtlLocale() helper. |
src/hooks/useDirection.test.ts |
Add tests for locale detection and DOM/ref-based direction resolution & updates. |
src/hooks/index.ts |
Export useDirection API from the hooks barrel. |
scripts/rtl-scan.mjs |
Add CI guard script to detect new physical-direction Tailwind utilities in components. |
scripts/rtl-codemod.mjs |
Add codemod to rewrite physical→logical utilities and flag manual-review cases. |
scripts/rtl-baseline.json |
Add initial baseline counts for the RTL scan ratchet. |
package.json |
Add rtl:scan and rtl:scan:update scripts. |
.storybook/preview.tsx |
Add direction global (auto/ltr/rtl), derive auto direction from locale, and sync dir/lang on <html>. |
.github/workflows/ci.yml |
Run RTL scan guard in CI. |
This was referenced Aug 5, 2026
…ag rtl:*-reverse remedies The optional suffix group could match empty, so the rtl:space-x-reverse remedy itself matched as a spurious bare 'space-x' manual site that isHandledReverse (which tests /^space-x-/) never exempted. Adopt rtl-scan.mjs's alternation: space-x- requires a real suffix, divide-x a trailing boundary.
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (2)
scripts/rtl-scan.mjs:35
- The scan regex places the optional negative sign before variant prefixes, so it will NOT match common Tailwind forms like
md:-ml-2/hover:-mr-4(negative comes after variants). That means the RTL guard can miss newly introduced negative physical-direction utilities whenever they’re combined with variants.
'(^|[\\s\'"`{:])-?(?:[a-z-]+:)*(' +
scripts/rtl-codemod.mjs:90
- The codemod’s detection regex also puts the optional negative sign before variant prefixes, so it won’t rewrite classes like
md:-ml-2(negative comes aftermd:). This will leave some physical-direction utilities un-migrated even though the script claims to preserve variants + negatives.
'(^|[\\s\'"`{:])(-?(?:[a-z-]+:)*)(' +
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 1 of the RTL series for #319, superseding #320 (which is being split into small targeted PRs).
What's here (infrastructure only — zero component changes)
scripts/rtl-scan.mjs— ratchet guard against new physical-direction classes, wired into CI (pnpm rtl:scan). Baseline generated at current main: 520 matches / 126 files; each component PR tightens it viapnpm rtl:scan:update.scripts/rtl-codemod.mjs— physical→logical Tailwind class rewriter (ml-*→ms-*,left-*→start-*,rounded-l-*→rounded-s-*, …) used to produce the follow-up component PRs. Dry-run by default;--writeapplies.useDirectionhook +isRtlLocale/RTL_LOCALES— observe the effective direction of the document or an element (MutationObserver-backed), exported from the hooks index.cn()tailwind-merge conflicts — physical⇄logical pairs (e.g.ml-*vsms-*) now conflict, so consumerclassName="ml-2"overrides keep working against migrated components.miewebUITwMergeConflictsexported for consumers with their own twMerge config.dir/langsynced on<html>including docs-only pages.Follow-up PRs (each ~4–6 components, each tightens the baseline)
Tailwind preset safelist entries intentionally ship with their component PRs, not here.
Validation
pnpm typecheck✅pnpm lint✅pnpm format✅pnpm rtl:scanpasses at the fresh baseline ✅